package graphics.Meshes;

import graphics.GraphicalObject;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;

import javax.media.opengl.GL;

import com.sun.opengl.util.texture.Texture;
import com.sun.opengl.util.texture.TextureIO;
import common.Math3D;
import common.Parameterization2D;
import common.Point3D;


public class AbstractMesh extends GraphicalObject {

	protected ArrayList<Point3D> points = new ArrayList<Point3D>();
	protected ArrayList<Trigon> trigons = new ArrayList<Trigon>();
	protected HashMap<Point3D, Point3D> normals = new HashMap<Point3D, Point3D>();

	String primaryTextFileName = null;
	Texture primaryTexture = null;
	protected boolean primaryTrigonsTexture = false;

	String secondaryTextFileName = null;
	Texture secondaryTexture = null;
	protected boolean secondaryTrigonsTexture = false;


	boolean isFlat = false;

	int glListDescriptor = -1;
	GL glListHolder = null;


	protected Point3D calculateMidpoint(Edge edge){
		double mnorm = ( edge.p0.norm() + edge.p1.norm() ) / 2;
		Point3D mp = Math3D.getAveragePoint(edge.p0, edge.p1);
		mp.normalize(mnorm);
		return mp;
	}

	private Point3D locateMidPoint(HashMap<Point3D, HashMap<Point3D, Point3D>> midpoints, Point3D p0, Point3D p1){
		Point3D m = midpoints.get(p0).get(p1);
		if (m == null) 
			m = midpoints.get(p1).get(p0);
		return m;
	}

	protected Point3D getMidPoint(HashMap<Point3D, HashMap<Point3D, Point3D>> midpoints, Point3D p0, Point3D p1, ArrayList<Point3D> pointsToAdd){
		Point3D m;
		if (midpoints.containsKey(p0) && midpoints.containsKey(p1)){
			m = locateMidPoint(midpoints, p0, p1);
			if (m!= null) return m;
		}

		if (!midpoints.containsKey(p0))
			midpoints.put(p0, new HashMap<Point3D, Point3D>());

		if (!midpoints.containsKey(p1))
			midpoints.put(p1, new HashMap<Point3D, Point3D>());

		Edge e = new Edge(p0, p1);
		m = calculateMidpoint(e);

		midpoints.get(p1).put(p0, m);
		midpoints.get(p0).put(p1, m);

		pointsToAdd.add(m);
		return m;
	}



	protected void subDivide(){
		ArrayList<Trigon> midtrigons = new ArrayList<Trigon>();
		HashMap<Point3D, HashMap<Point3D, Point3D>> midpoints = new HashMap<Point3D, HashMap<Point3D,Point3D>>();
		ArrayList<Point3D> pointsToAdd = new ArrayList<Point3D>();
		for (Trigon trigon : trigons){

			Point3D m01 = getMidPoint(midpoints, trigon.p0, trigon.p1, pointsToAdd);
			Point3D m12 = getMidPoint(midpoints, trigon.p1, trigon.p2, pointsToAdd);
			Point3D m20 = getMidPoint(midpoints, trigon.p2, trigon.p0, pointsToAdd);

			if (m01 == null || m12 == null || m20 == null)
				System.out.println("eeadfsadfsd");

			midtrigons.add(new Trigon(trigon.p0,m01,m20));
			midtrigons.add(new Trigon(trigon.p1,m12,m01));
			midtrigons.add(new Trigon(trigon.p2,m20,m12));
			midtrigons.add(new Trigon(m01,m12,m20));
		}

		this.points.addAll(pointsToAdd);
		this.trigons.clear();
		this.trigons.addAll(midtrigons);
	}

	@Override
	public void initialize(GL gl) {
		super.initialize(gl);

		if (primaryTextFileName!= null && primaryTexture == null){
			try {
				File TextureFile = new File(primaryTextFileName); 
				primaryTexture = TextureIO.newTexture(TextureFile, true);
			} catch (Exception e) {
				System.out.println("Error failed to primary read texture");
				e.printStackTrace();
				primaryTexture = null;
			}}

		if (secondaryTextFileName!= null && secondaryTexture == null){
			try {
				File TextureFile = new File(secondaryTextFileName); 
				secondaryTexture = TextureIO.newTexture(TextureFile, true);
			} catch (Exception e) {
				System.out.println("Error failed to read secondary texture");
				e.printStackTrace();
				secondaryTexture = null;
			}}

		glListDescriptor = gl.glGenLists(1);
		glListHolder = gl;

		gl.glNewList(glListDescriptor, GL.GL_COMPILE);
		this.calculateVertexNormals();

		gl.glPushAttrib(GL.GL_LIGHTING_BIT);

		if (isFlat){
			gl.glShadeModel(GL.GL_FLAT);
		}

		gl.glMaterialfv(GL.GL_FRONT, GL.GL_AMBIENT, materialAmbient, 0);
		gl.glMaterialfv(GL.GL_FRONT, GL.GL_SPECULAR, materialSpecular, 0);
		gl.glMaterialfv(GL.GL_FRONT, GL.GL_EMISSION, materialEmission, 0);

		if (primaryTexture != null){
			primaryTexture.enable();
			primaryTexture.bind();
			this.renderTrigons(gl,primaryTrigonsTexture);
			primaryTexture.disable();
		}

		if (secondaryTexture != null){
			if (primaryTexture == null){
				secondaryTexture.enable();
				secondaryTexture.bind();
				this.renderTrigons(gl,secondaryTrigonsTexture);
				secondaryTexture.disable();
			}
			else {
				secondaryTexture.enable();
				secondaryTexture.bind();
				gl.glEnable(GL.GL_BLEND);
				gl.glBlendFunc(GL.GL_SRC_COLOR, GL.GL_ONE);
				this.renderTrigons(gl,secondaryTrigonsTexture);
				gl.glDisable(GL.GL_BLEND);
				secondaryTexture.disable();
			}
		}

		if (isFlat){
			gl.glShadeModel(GL.GL_SMOOTH);
		}

		gl.glPopAttrib();
		gl.glEndList();
	}

	@Override
	protected void finalize() throws Throwable {
		glListHolder.glDeleteLists(glListDescriptor, 1);
		super.finalize();
	}

	@Override
	public void render(GL gl) {

		super.render(gl);
		gl.glCallList(glListDescriptor);
	}

	private void renderTrigons(GL gl, boolean tiles) {

		gl.glBegin(GL.GL_TRIANGLES);
		for (Trigon trigon : trigons){

			Parameterization2D t1pmz0 = parameterizePoint(trigon.p0);
			Parameterization2D t1pmz1 = parameterizePoint(trigon.p1);
			Parameterization2D t1pmz2 = parameterizePoint(trigon.p2);
			reparametrize(t1pmz0, t1pmz1, t1pmz2, trigon, tiles);

			Point3D normal;

			normal = normals.get(trigon.p0);
			gl.glNormal3d(normal.x, normal.y, normal.z);
			gl.glTexCoord2d(t1pmz0.getX(),t1pmz0.getY());
			gl.glVertex3d(trigon.p0.x, trigon.p0.y, trigon.p0.z);

			normal = normals.get(trigon.p1);
			gl.glNormal3d(normal.x, normal.y, normal.z);
			gl.glTexCoord2d(t1pmz1.getX(),t1pmz1.getY());
			gl.glVertex3d(trigon.p1.x, trigon.p1.y, trigon.p1.z);

			normal = normals.get(trigon.p2);
			gl.glNormal3d(normal.x, normal.y, normal.z);
			gl.glTexCoord2d(t1pmz2.getX(),t1pmz2.getY());
			gl.glVertex3d(trigon.p2.x, trigon.p2.y, trigon.p2.z);
		}
		gl.glEnd();
	}


	@Override
	public void setParameter(String name, String[] params) {
		if (name.equals("texture")) {
			setTexture1File(params[0]);	
			return;
		}

		if (name.equals("texture2")) {
			setTexture2File(params[0]);	
			return;
		}

		if (name.equals("trigonTiles")){
			primaryTrigonsTexture  = Boolean.parseBoolean(params[0]);
			return;
		}
		if (name.equals("trigonTiles2")){
			secondaryTrigonsTexture  = Boolean.parseBoolean(params[0]);
			return;
		}

		if (name.equals("flat")){
			isFlat  = Boolean.parseBoolean(params[0]);
			return;
		}
		super.setParameter(name, params);
	}

	private double calcWidthAngle(Point3D point){
		Point3D v = point.clone();
		v.y = 0;
		double angle;
		if (v.x == 0 && v.z == 0){ 
			angle = 0;
		}
		else{
			v.normalize();
			angle = Math.acos(Math3D.getDotProduct(new Point3D(1, 0, 0), v));
		}
		if (point.z >= 0) return angle;
		else return 2.0 * Math.PI - angle;
	}

	private double calcHeightAngle(Point3D point){
		Point3D v = point.clone();
		v.normalize();
		return Math.acos(Math3D.getDotProduct(new Point3D(0, 1, 0), v));
	}

	public Parameterization2D parameterizePoint(Point3D point){
		return new Parameterization2D ( calcWidthAngle(point) / (2*Math.PI) / 2, calcHeightAngle(point) / Math.PI );
	}

	protected void reparametrize(Parameterization2D p0, Parameterization2D p1, Parameterization2D p2, Trigon trigon, boolean isTiles){
		return;
	}


	private void calculateVertexNormals(){

		HashMap<Point3D, Integer> count = new HashMap<Point3D, Integer>();

		normals.clear();
		for (Point3D point : points){
			normals.put(point, new Point3D(0,0,0));
			count.put(point, 0);
		}

		for (Trigon trigon : trigons){
			normals.get(trigon.p0).add(trigon.getNormal());
			normals.get(trigon.p1).add(trigon.getNormal());
			normals.get(trigon.p2).add(trigon.getNormal());
			count.put(trigon.p0, count.get(trigon.p0) + 1);
			count.put(trigon.p1, count.get(trigon.p1) + 1);
			count.put(trigon.p2, count.get(trigon.p2) + 1);
		}

		for (Point3D normal : normals.values()){
			normal.normalize();
		}

		for (Point3D point : points){
			int i = count.get(point);
		}

	}

	public ArrayList<Point3D> getPoints() {
		return points;
	}

	public ArrayList<Trigon> getTrigons() {
		return trigons;
	}

	public void setTexture1File(String textFileName) {
		this.primaryTextFileName = textFileName;
	}
	public void setTexture2File(String textFileName) {
		this.secondaryTextFileName = textFileName;
	}
}


